#include <common.h>
#include <malloc.h>
#include <asm/io.h>
#include <asm/arch/hardware.h>
#include <fh_dma.h>

#ifndef MIN
#define MIN(a, b) (((a) < (b)) ? (a) : (b))
#endif
/*#define DMA_DEBUG*/ 
#if defined(DMA_DEBUG)
#define FH_DMA_DEBUG(fmt, args...) printf(fmt, ##args);
#else
#define FH_DMA_DEBUG(fmt, args...)
#endif


#define FH_DMA_ASSERT(expr) if (!(expr)) { \
        printf("Assertion failed! %s:line %d\n", \
        __func__, __LINE__); \
        while (1)   \
           ;       \
        }


#define DMA_CONTROLLER_NUMBER (1)

/*********************************
 *
 * copy from the linux core start
 *
 *********************************/
/* this is the ip reg offset....don't change!!!!!!! */
#define DW_DMA_MAX_NR_CHANNELS 8

/*
 * Redefine this macro to handle differences between 32- and 64-bit
 * addressing, big vs. little endian, etc.
 */
#define DW_REG(name)  \
    u32 name; \
    u32 __pad_##name

/* Hardware register definitions. */
struct dw_dma_chan_regs
{
    DW_REG(SAR);        /* Source Address Register */
    DW_REG(DAR);        /* Destination Address Register */
    DW_REG(LLP);        /* Linked List Pointer */
    u32 CTL_LO; /* Control Register Low */
    u32 CTL_HI; /* Control Register High */

    DW_REG(SSTAT);
    DW_REG(DSTAT);
    DW_REG(SSTATAR);
    DW_REG(DSTATAR);
    u32 CFG_LO; /* Configuration Register Low */
    u32 CFG_HI; /* Configuration Register High */

    DW_REG(SGR);
    DW_REG(DSR);
};

struct dw_dma_irq_regs
{
    DW_REG(XFER);
    DW_REG(BLOCK);
    DW_REG(SRC_TRAN);
    DW_REG(DST_TRAN);
    DW_REG(ERROR);
};

struct dw_dma_regs
{
    /* per-channel registers */
    struct dw_dma_chan_regs CHAN[DW_DMA_MAX_NR_CHANNELS];

    /* irq handling */
    struct dw_dma_irq_regs RAW;    /* r */
    struct dw_dma_irq_regs STATUS; /* r (raw & mask) */
    struct dw_dma_irq_regs MASK;   /* rw (set = irq enabled) */
    struct dw_dma_irq_regs CLEAR;  /* w (ack, affects "raw") */

    DW_REG(STATUS_INT); /* r */

    /* software handshaking */
    DW_REG(REQ_SRC);
    DW_REG(REQ_DST);
    DW_REG(SGL_REQ_SRC);
    DW_REG(SGL_REQ_DST);
    DW_REG(LAST_SRC);
    DW_REG(LAST_DST);

    /* miscellaneous */
    DW_REG(CFG);
    DW_REG(CH_EN);
    DW_REG(ID);
    DW_REG(TEST);

    /* optional encoded params, 0x3c8..0x3 */
};
/* Bitfields in CTL_LO */
#define DWC_CTLL_INT_EN (1 << 0)         /* irqs enabled? */
#define DWC_CTLL_DST_WIDTH(n) ((n) << 1) /* bytes per element */
#define DWC_CTLL_SRC_WIDTH(n) ((n) << 4)

#define DWC_CTLL_DST_INC_MODE(n) ((n) << 7)

#define DWC_CTLL_DST_INC (0 << 7) /* DAR update/not */
#define DWC_CTLL_DST_DEC (1 << 7)
#define DWC_CTLL_DST_FIX (2 << 7)

#define DWC_CTLL_SRC_INC_MODE(n) ((n) << 9)

#define DWC_CTLL_SRC_INC (0 << 9) /* SAR update/not */
#define DWC_CTLL_SRC_DEC (1 << 9)
#define DWC_CTLL_SRC_FIX (2 << 9)
#define DWC_CTLL_DST_MSIZE(n) ((n) << 11) /* burst, #elements */
#define DWC_CTLL_SRC_MSIZE(n) ((n) << 14)
#define DWC_CTLL_S_GATH_EN (1 << 17) /* src gather, !FIX */
#define DWC_CTLL_D_SCAT_EN (1 << 18) /* dst scatter, !FIX */
#define DWC_CTLL_FC(n) ((n) << 20)
#define DWC_CTLL_FC_M2M (0 << 20) /* mem-to-mem */
#define DWC_CTLL_FC_M2P (1 << 20) /* mem-to-periph */
#define DWC_CTLL_FC_P2M (2 << 20) /* periph-to-mem */
#define DWC_CTLL_FC_P2P (3 << 20) /* periph-to-periph */
/* plus 4 transfer types for peripheral-as-flow-controller */
#define DWC_CTLL_DMS(n) ((n) << 23) /* dst master select */
#define DWC_CTLL_SMS(n) ((n) << 25) /* src master select */
#define DWC_CTLL_LLP_D_EN (1 << 27) /* dest block chain */
#define DWC_CTLL_LLP_S_EN (1 << 28) /* src block chain */

/* Bitfields in CTL_HI */
#define DWC_CTLH_DONE 0x00001000
#define DWC_CTLH_BLOCK_TS_MASK 0x00000fff

/* Bitfields in CFG_LO. Platform-configurable bits are in <linux/dw_dmac.h> */
#define DWC_CFGL_CH_PRIOR_MASK (0x7 << 5) /* priority mask */
#define DWC_CFGL_CH_PRIOR(x) ((x) << 5)   /* priority */
#define DWC_CFGL_CH_SUSP (1 << 8)         /* pause xfer */
#define DWC_CFGL_FIFO_EMPTY (1 << 9)      /* pause xfer */

#define DWC_CFGL_HS_DST (1 << 10) /* handshake w/dst */
#define DWC_CFGL_HS_SRC (1 << 11) /* handshake w/src */
#define DWC_CFGL_MAX_BURST(x) ((x) << 20)
#define DWC_CFGL_RELOAD_SAR (1 << 30)
#define DWC_CFGL_RELOAD_DAR (1 << 31)

/* Bitfields in CFG_HI. Platform-configurable bits are in <linux/dw_dmac.h> */
#define DWC_CFGH_DS_UPD_EN (1 << 5)
#define DWC_CFGH_SS_UPD_EN (1 << 6)

/* Bitfields in SGR */
#define DWC_SGR_SGI(x) ((x) << 0)
#define DWC_SGR_SGC(x) ((x) << 20)

/* Bitfields in DSR */
#define DWC_DSR_DSI(x) ((x) << 0)
#define DWC_DSR_DSC(x) ((x) << 20)

/* Bitfields in CFG */
#define DW_CFG_DMA_EN (1 << 0)

#define DW_REGLEN 0x400

/* Platform-configurable bits in CFG_HI */
#define DWC_CFGH_FCMODE (1 << 0)
#define DWC_CFGH_FIFO_MODE (1 << 1)
#define DWC_CFGH_PROTCTL(x) ((x) << 2)
#define DWC_CFGH_SRC_PER(x) ((x) << 7)
#define DWC_CFGH_DST_PER(x) ((x) << 11)

/* Platform-configurable bits in CFG_LO */
#define DWC_CFGL_LOCK_CH_XFER (0 << 12) /* scope of LOCK_CH */
#define DWC_CFGL_LOCK_CH_BLOCK (1 << 12)
#define DWC_CFGL_LOCK_CH_XACT (2 << 12)
#define DWC_CFGL_LOCK_BUS_XFER (0 << 14) /* scope of LOCK_BUS */
#define DWC_CFGL_LOCK_BUS_BLOCK (1 << 14)
#define DWC_CFGL_LOCK_BUS_XACT (2 << 14)
#define DWC_CFGL_LOCK_CH (1 << 15)    /* channel lockout */
#define DWC_CFGL_LOCK_BUS (1 << 16)   /* busmaster lockout */
#define DWC_CFGL_HS_DST_POL (1 << 18) /* dst handshake active low */
#define DWC_CFGL_HS_SRC_POL (1 << 19) /* src handshake active low */
#ifndef lift_shift_bit_num
#define lift_shift_bit_num(bit_num) (1 << bit_num)
#endif
#define __dma_raw_writeb(v, a) (*(volatile unsigned char *)(a) = (v))
#define __dma_raw_writew(v, a) (*(volatile unsigned short *)(a) = (v))
#define __dma_raw_writel(v, a) (*(volatile unsigned int *)(a) = (v))

#define __dma_raw_readb(a) (*(volatile unsigned char *)(a))
#define __dma_raw_readw(a) (*(volatile unsigned short *)(a))
#define __dma_raw_readl(a) (*(volatile unsigned int *)(a))

#define dw_readl(dw, name) \
    __dma_raw_readl(&(((struct dw_dma_regs *)dw->regs)->name))
#define dw_writel(dw, name, val) \
    __dma_raw_writel((val), &(((struct dw_dma_regs *)dw->regs)->name))
#define dw_readw(dw, name) \
    __dma_raw_readw(&(((struct dw_dma_regs *)dw->regs)->name))
#define dw_writew(dw, name, val) \
    __dma_raw_writew((val), &(((struct dw_dma_regs *)dw->regs)->name))

#define CHANNEL0 (lift_shift_bit_num(0))
#define CHANNEL1 (lift_shift_bit_num(1))
#define CHANNEL2 (lift_shift_bit_num(2))
#define CHANNEL3 (lift_shift_bit_num(3))

#define channel_set_bit(dw, reg, mask) \
    dw_writel(dw, reg, ((mask) << 8) | (mask))
#define channel_clear_bit(dw, reg, mask) dw_writel(dw, reg, ((mask) << 8) | 0)



struct dw_dma
{
    void *regs;
};


//global var below...
struct dw_dma g_dma_obj;
struct dw_lli *p_spi_tx = 0;
struct dw_lli *p_spi_rx = 0;

#define DMA_LLI_SIZE        4096

static __attribute__((aligned(32))) struct dw_lli g_dma_tx_lli[DMA_LLI_SIZE];
static __attribute__((aligned(32))) struct dw_lli g_dma_rx_lli[DMA_LLI_SIZE];


void *get_dma_tx_lli_head(void){
    return (void *)p_spi_tx;
}

void *get_dma_rx_lli_head(void){
    return (void *)p_spi_rx;
}

void dma_init(void){
    g_dma_obj.regs = (void *)DMAC0_REG_BASE;
    struct dw_dma *temp_dwc;
    temp_dwc = &g_dma_obj;
    p_spi_tx = (struct dw_lli *)&g_dma_tx_lli[0];
    p_spi_rx = (struct dw_lli *)&g_dma_rx_lli[0];
    dw_writel(temp_dwc, CFG, 1);
}


void wait_dma_xfer_done(u32 channel){
    struct dw_dma *temp_dwc;
    u32 isr_channel_x;

    temp_dwc = &g_dma_obj;
    channel = 1 << channel;

    do{
         isr_channel_x = dw_readl(temp_dwc, RAW.XFER);
    }while((isr_channel_x & channel) != channel);
   
 	dw_writel(temp_dwc, CLEAR.XFER, isr_channel_x);

}

static void dump_lli(struct dw_lli *p_lli)
{
    FH_DMA_DEBUG(
        "link_mem padd:0x%x\n sar:0x%x\n dar:0x%x\n llp:0x%x\n ctllo:0x%x\n ctlhi:0x%x\n sstat:0x%x\n dstat:0x%x\n",
        (u32)p_lli, p_lli->sar, p_lli->dar, p_lli->llp, p_lli->ctllo,
        p_lli->ctlhi, p_lli->sstat, p_lli->dstat);
}


static u32 cal_lli_size(struct dma_transfer *p_transfer)
{
 
    u32 lli_number                = 0;
    u32 channel_max_trans_per_lli = 0;
    u32 c_msize;
    c_msize = FH_CHANNEL_MAX_TRANSFER_SIZE;
    channel_max_trans_per_lli = (p_transfer->period_len != 0) ? 
    (MIN(p_transfer->period_len, c_msize)) : (c_msize);
    lli_number = (p_transfer->trans_len % channel_max_trans_per_lli) ? 1 : 0;
    lli_number += p_transfer->trans_len / channel_max_trans_per_lli;
    FH_DMA_DEBUG("cal lli size is %x\n",lli_number);
    return lli_number;
}


void handle_single_transfer(struct dma_transfer *p_transfer,void *p_lli_in)
{

    u32 i;
    u32 lli_cal_size;
    volatile u32 ret_status;
    struct dw_dma *temp_dwc;
    u32 temp_src_add = 0;
    u32 temp_dst_add = 0;
    u32 trans_total_len = 0;
    u32 temp_trans_size = 0;
    u32 max_trans_size = 0;;
    temp_dwc = &g_dma_obj;
    struct dw_lli *p_lli = 0;
    struct dma_transfer *dma_trans_desc = p_transfer;
    trans_total_len = p_transfer->trans_len;
    lli_cal_size = cal_lli_size(p_transfer);
    FH_DMA_ASSERT(lli_cal_size <= DMA_LLI_SIZE);
    //if code fix done....the set mem below could set only once...do not need 1:set zero 2:set correct data. this will cost 15ms  M or L
    memset((void *)p_lli_in, 0,lli_cal_size * sizeof(struct dw_lli));
    p_lli = (void *)p_lli_in;
    max_trans_size = FH_CHANNEL_MAX_TRANSFER_SIZE;
    if(p_transfer->period_len != 0){
        max_trans_size = MIN(max_trans_size, p_transfer->period_len);
    }
    for (i = 0; i < lli_cal_size; i++)
    {
        /* parse trans para... */
        /* para add: */
        switch (dma_trans_desc->dst_inc_mode)
        {
        case DW_DMA_SLAVE_INC:
            temp_dst_add =
                dma_trans_desc->dst_add +
                i * max_trans_size * (1 << dma_trans_desc->src_width);
            break;
        case DW_DMA_SLAVE_DEC:
            temp_dst_add =
                dma_trans_desc->dst_add -
                i * max_trans_size * (1 << dma_trans_desc->src_width);
            break;
        case DW_DMA_SLAVE_FIX:
            temp_dst_add = dma_trans_desc->dst_add;
            break;
        }

        switch (dma_trans_desc->src_inc_mode)
        {
        case DW_DMA_SLAVE_INC:
            temp_src_add =
                dma_trans_desc->src_add +
                i * max_trans_size * (1 << dma_trans_desc->src_width);
            break;
        case DW_DMA_SLAVE_DEC:
            temp_src_add =
                dma_trans_desc->src_add -
                i * max_trans_size * (1 << dma_trans_desc->src_width);
            break;
        case DW_DMA_SLAVE_FIX:
            temp_src_add = dma_trans_desc->src_add;
            break;
        }
        if(p_transfer->src_reload_flag == ADDR_RELOAD){
            temp_src_add = dma_trans_desc->src_add;
        }
        if(p_transfer->dst_reload_flag == ADDR_RELOAD){
            temp_dst_add = dma_trans_desc->dst_add;
        }
        p_lli[i].sar = temp_src_add;
        p_lli[i].dar = temp_dst_add;
        /* para ctl */
        temp_trans_size = (trans_total_len / max_trans_size)
                                ? max_trans_size
                                : (trans_total_len % max_trans_size);
        trans_total_len -= temp_trans_size;
        p_lli[i].ctllo =
            DWC_CTLL_DST_WIDTH(dma_trans_desc->dst_width) |
            DWC_CTLL_SRC_WIDTH(dma_trans_desc->src_width) |
            DWC_CTLL_DST_INC_MODE(dma_trans_desc->dst_inc_mode) |
            DWC_CTLL_SRC_INC_MODE(dma_trans_desc->src_inc_mode) |
            DWC_CTLL_DST_MSIZE(dma_trans_desc->dst_msize) |
            DWC_CTLL_SRC_MSIZE(dma_trans_desc->src_msize) |
            DWC_CTLL_FC(dma_trans_desc->fc_mode) |
            DWC_CTLL_DMS(dma_trans_desc->dst_master_sel) |
            DWC_CTLL_SMS(dma_trans_desc->src_master_sel);

        /* block size */
        p_lli[i].ctlhi = temp_trans_size;

        if (trans_total_len > 0)
        {
            p_lli[i].llp = (u32)&p_lli[i + 1];
            p_lli[i].ctllo |= DWC_CTLL_LLP_D_EN | DWC_CTLL_LLP_S_EN;
        }

        dump_lli(&p_lli[i]);
    }
    // set the dma config reg
    // clear cfg reload reg
    // ret_status =
    // dw_readl(temp_dwc,CHAN[dma_trans_desc->channel_number].CFG_LO);
    // ret_status &= ~(DWC_CFGL_RELOAD_SAR|DWC_CFGL_RELOAD_DAR);
    dw_writel(temp_dwc, CHAN[dma_trans_desc->channel_number].CFG_LO, 0);
    // set the first link add
    // ret_status =
    // dw_readl(temp_dwc,CHAN[dma_trans_desc->channel_number].LLP);
    ret_status = 0;
    ret_status = (u32)&p_lli[0];
    dw_writel(temp_dwc, CHAN[dma_trans_desc->channel_number].LLP,
                ret_status);

    // set link enable
    // ret_status =
    // dw_readl(temp_dwc,CHAN[dma_trans_desc->channel_number].CTL_LO);
    ret_status = 0;
    ret_status = DWC_CTLL_LLP_D_EN | DWC_CTLL_LLP_S_EN;
    dw_writel(temp_dwc, CHAN[dma_trans_desc->channel_number].CTL_LO,
                ret_status);
    dw_writel(temp_dwc, CHAN[dma_trans_desc->channel_number].CTL_HI, 0);
    // set handshaking
    ret_status =
        dw_readl(temp_dwc, CHAN[dma_trans_desc->channel_number].CFG_LO);
    ret_status &= ~DWC_CFGL_HS_SRC;
    ret_status &= ~DWC_CFGL_HS_DST;
   // ret_status |= DWC_CFGL_CH_PRIOR(p_transfer->priority);
   // printf("channel no :%d set cfg low is %x\n",dma_trans_desc->channel_number,ret_status);
    dw_writel(temp_dwc, CHAN[dma_trans_desc->channel_number].CFG_LO,
                ret_status);

    // only hw handshaking need this..
    switch (dma_trans_desc->fc_mode)
    {
    case DMA_M2M:
        ret_status =
            dw_readl(temp_dwc, CHAN[dma_trans_desc->channel_number].CFG_HI);
        /* ahb to axi bus mode*/
        ret_status &= ~(7<<2);
        ret_status |= 2<<2;
        /* fifo enable mode*/
        ret_status |= 1<<1;
        dw_writel(temp_dwc, CHAN[dma_trans_desc->channel_number].CFG_HI,
            ret_status);
        break;
    case DMA_M2P:
        // set dst per...
        ret_status =
            dw_readl(temp_dwc, CHAN[dma_trans_desc->channel_number].CFG_HI);
        // clear 43 ~ 46 bit
        ret_status &= ~0x7800;
        ret_status |= DWC_CFGH_DST_PER(dma_trans_desc->dst_per);
        //new add 
        ret_status &= ~(7<<2);
        ret_status |= 2<<2;
       // printf("cfg hi data is %x\n",ret_status);
        dw_writel(temp_dwc, CHAN[dma_trans_desc->channel_number].CFG_HI,
                    ret_status);
        // DWC_CFGH_SRC_PER

        break;
    case DMA_P2M:
        // set src per...
        ret_status =
            dw_readl(temp_dwc, CHAN[dma_trans_desc->channel_number].CFG_HI);
        // clear 39 ~ 42 bit
        ret_status &= ~0x780;
        ret_status |= DWC_CFGH_SRC_PER(dma_trans_desc->src_per);

          //new add 
        ret_status &= ~(7<<2);
        ret_status |= 2<<2;
         //printf("cfg hi data is %x\n",ret_status);
        dw_writel(temp_dwc, CHAN[dma_trans_desc->channel_number].CFG_HI,
                    ret_status);
        break;
    default:
        break;
    }

    // close
    channel_clear_bit(temp_dwc, MASK.BLOCK,
                        lift_shift_bit_num(dma_trans_desc->channel_number));

    dw_writel(temp_dwc, CLEAR.XFER, 1 << (dma_trans_desc->channel_number));
    dw_writel(temp_dwc, CLEAR.BLOCK, 1 << (dma_trans_desc->channel_number));
    dw_writel(temp_dwc, CLEAR.SRC_TRAN,
                1 << (dma_trans_desc->channel_number));
    dw_writel(temp_dwc, CLEAR.DST_TRAN,
                1 << (dma_trans_desc->channel_number));
    dw_writel(temp_dwc, CLEAR.ERROR, 1 << (dma_trans_desc->channel_number));

    // enable the channle to transfer
    channel_set_bit(temp_dwc, CH_EN,
                    lift_shift_bit_num(dma_trans_desc->channel_number));
 
}
